﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//动态内存管理
//内存开辟⽅式有：
//int a = 10;//在栈空间上开辟四个字节
//int arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间 

//但是上述开辟空间的方式有两个特点：空间开辟大小是固定的；数组在声明时必须指定数组的长度，他所需要的内存在编译时分配
//但有时候我们需要的空间大小在程序运行的时候才能知道，那数组在编译时开辟空间的方式就不满足，这时候只能试试动态内存开辟了

//动态内存函数介绍
//1. malloc和free

//1.1 malloc
//void* malloc (size_t size);
//这个函数向内存申请⼀块连续可⽤的空间，并返回指向这块空间的指针。如果开辟失败，返回NULL指针

//1.2 free
//C语⾔提供了另外⼀个函数free，专⻔是⽤来做动态内存的释放和回收的，函数原型如下：
//void free (void* ptr);

#include <stdlib.h>
#include <string.h>
#include <errno.h>
//int main()
//{
//	//申请40个字节，用来存放10个整数
//	int* p = (int*)malloc(40);
//	//int* p = (int*)malloc(INT_MAX);
//
//	if (p == NULL)
//	{
//		printf("%s\n", strerror(errno));
//		return 1;
//	}
//	//存放1~10
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*(p + i) = i + 1;
//	}
//	//打印
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//	//free是释放申请的内存
//	free(p);//释放p所指向的动态内存 ,但 p 仍指向原地址
//	p = NULL;//显式置空，避免悬空指针
//	return 0;
//}

//2. calloc
//void* calloc (size_t num, size_t size);
//函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间，并且把空间的每个字节初始化为0
//与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
//int main()
//{
//	int* p = (int*)calloc(10, sizeof(int));
//	if (p == NULL)
//	{
//		perror("calloc");
//		return 1;
//	}
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//	free(p);
//	p = NULL;
//	return 0;
//}
//malloc和calloc都是在堆区上申请空间，空间使用完都要释放

//3.realloc
//void* realloc (void* ptr, size_t size);
//realloc函数的出现让动态内存管理更加灵活。realloc 函数可以做到对动态开辟内存⼤⼩的调整。
//ptr 是要调整的内存地址,size 调整之后新⼤⼩;返回值为调整之后的内存起始位置。
//这个函数调整原内存空间⼤⼩的基础上，还会将原来内存中的数据移动到 新 的空间。
//int main()
//{
//	int* p = (int*)malloc(5 * sizeof(int));
//	if (p == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	//使用
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		*(p + i) = 1;
//	}
//	//不够了，增加5个整型的空间
//	int* ptr = (int*)realloc(p, 10 * sizeof(int));
//	if (ptr != NULL)
//	{
//		p = ptr;// 更新指针为新地址
//		//用 realloc 返回的新指针 ptr 覆盖原指针 p，使得 p 指向新的内存地址，而不再指向原来的地址。
//	}
//	//继续使用空间
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *(p + i));
//	}
//	//释放空间
//	free(p);
//	p = NULL;
//
//	return 0;
//}

//如果realloc的第一个参数为空指针NULL，则它的作用就是malloc
//realloc(NULL, 40) = malloc(40)

//常见的动态内存错误
//1.对NULL指针的解引用操作
//int main()
//{
//	int* p = (int*)malloc(40);
//	//malloc返回值一定要判断
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		*(p + i) = 0;//如果p的值是NULL，就会有问题 
//	}
//	return 0;
//}

//2.对动态开辟空间的越界访问
//int main()
//{
//	int* p = (int*)malloc(100);
//	if (p = NULL)
//	{
//		return 1;
//	}
//	int i = 0;
//	//越界访问
//	for (i = 0; i < 100; i++)//开辟了100个字节的空间，即25个整型，并非100个整型
//	{
//		//*(p + 1) = 1;
//		p[i] = 0;
//	}
//	free(p);
//	p = NULL;
//	return 0;
//}

//3.对非动态开辟内存使用free释放
//int main()
//{
//	int a = 10;//栈区
//	int* p = &a;
//
//	free(p);
//	p = NULL;
//
//	return 0;
//}

//4.使用free释放一块东方台开辟内存的一部分
//int main()
//{
//	int* p = (int*)malloc(100);
//	if (p == NULL)
//	{
//		return 1;
//	}
//	int i = 0;
//	for (i = 0; i < 25; i++)
//	{
//		*p = i;
//		p++;
//	}
//	free(p);//p不再指向动态内存的起始位置 
//	p = NULL;
//	return 0;
//}

//5.对同一块空间内存多次释放
//int main()
//{
//	int* p = (int*)malloc(100);
//	if (p == NULL)
//	{
//		return 1;
//	}
//	//使用
//	//释放
//	free(p);
//	//……
//
//	free(p);//重复释放
//	return 0;
//}
//要避免这个错误，在第一次释放完p后将其置为空指针，这样重复释放时，就不会有错误，因为参数为空时，free无事发生

//6.动态开辟内存忘记释放（内存泄漏）
//void test()
//{
//	int* p = (int*)malloc(100);
//	//……
//}
//int mian()
//{
//	test();//在test函数内使用内存函数开辟了空间，使用完后忘记释放，这时候想销毁来不及了，因为test函数调用完已经被释放了
//	//……        //（临时变量p已经销毁）已经不知道开辟的100个字节的地址在哪里了
//
//	return 0;
//}

//解决方法要么及时释放，如果还想传给别人进行使用，记得使用完也要及时释放

int* test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	return p;
}

int main()
{
	int* ptr = test();
	free(ptr);        
	ptr = NULL;
}
